#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <vector>
#include "task.hpp"

class Channel
{
public:
    Channel(int wfd, pid_t subPid, std::string name)
        :_wfd(wfd), _subPid(subPid), _name(name)
        {}
    
    // get
    int GetWfd() { return _wfd; }
    pid_t GetSubPid() { return _subPid; }
    std::string GetName() { return _name; }

    void CloseWfd() 
    {
        close(_wfd);
    }

    void Wait()
    {
        int rid = waitpid(_subPid, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait: " << rid << ", sucess!" << std::endl; 
        }
    }
private:
    int _wfd;
    pid_t _subPid;
    std::string _name;
};



// void CreateChannels(int num, std::vector<Channel>* channels)
// {
//     for (int i = 0; i < num; i++)
//     {
//         // 1.创建管道
//         int pipefd[2] = { 0 }; // 记录读写端fd
//         int n = pipe(pipefd);
//         if (n == -1) exit(1); // 创建失败

//         // 2.创建子进程
//         int id = fork();
//         if (id == 0)
//         {
//             // 子进程，关闭写端
//             close(pipefd[1]);
//             work(pipefd[0]); // 将读端交给 work，从管道读取任务并执行
//             close(pipefd[0]); // 任务执行完毕，关闭读端
//             exit(0); // 子进程到此为止，下面都是父进程的逻辑
//         }

//         // 父进程，关闭读端
//         close(pipefd[0]);
//         // 设置 Channel 的名字
//         std::string name = "channel-" + std::to_string(i);
//         // 将信道管理起来
//         channels->push_back(Channel(pipefd[1], id, name));
//     }
// }

void CreateChannels(int num, std::vector<Channel>* channels, task_t task)
{
    for (int i = 0; i < num; i++)
    {
        // 1.创建管道
        int pipefd[2] = { 0 }; // 记录读写端fd
        int n = pipe(pipefd);
        if (n == -1) exit(1); // 创建失败

        // 2.创建子进程
        int id = fork();
        if (id == 0)
        {
            // 关闭继承过来的写端
            for (auto& e : *channels)
            {
                e.CloseWfd();
            }
            // 子进程，关闭写端
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 将管道读端重定向到标准输出
            task(); // 任务解耦
            close(pipefd[0]); // 任务执行完毕，关闭读端
            exit(0); // 子进程到此为止，下面都是父进程的逻辑
        }

        // 父进程，关闭读端
        close(pipefd[0]);
        // 设置 Channel 的名字
        std::string name = "channel-" + std::to_string(i);
        // 将信道管理起来
        channels->push_back(Channel(pipefd[1], id, name));
    }
}

int NextChannel(int size)
{
    static int count = 0; // 用来轮询下标
    int ret = count; // 返回值
    count++; // 下一个信道下标
    count %= size; // 防止越界
    return ret;
}

void ControlChannelOnce(std::vector<Channel>& channels)
{
    sleep(1);
    // 选择任务
    int taskcmd = SelectTask();
    // 轮询选择信道
    int index_channel = NextChannel(channels.size());
    // 发送任务
    write(channels[index_channel].GetWfd(), &taskcmd, sizeof(taskcmd));

    // 将任务、信道、进程id打印出来看看
    std::cout << std::endl;
    std::cout << "taskcmd: " << taskcmd << ", channel: " << channels[index_channel].GetName() << ", sub process: " << channels[index_channel].GetSubPid() << std::endl;
}

void ControlChannel(std::vector<Channel>& channels, int num = -1)
{
    if (num > 0)
    {
        while(num--) 
            ControlChannelOnce(channels);
    }
    else
    {
        while(1) 
            ControlChannelOnce(channels);
    }
}

void CleanChannel(std::vector<Channel>& channels)
{
    for (auto& e : channels)
    {
        e.CloseWfd();
        e.Wait();
    }
    // 关闭写端
    // for (int i = channels.size()-1; i >= 0; i--)
    // {
    //     channels[i].CloseWfd();
    //     channels[i].Wait();
    // }
    // // 等待子进程
    // for (auto& e : channels)
    // {
    // }
}
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        // 程序用法错误
        std::cerr << "usage:" << argv[0] << " ProcessNum" << std::endl;
    }
    int num = std::stoi(argv[1]); // 需要创建的信道数量
    LoadTask();

    // 1.创建管道和子进程
    std::vector<Channel> channels;
    CreateChannels(num, &channels, work);

    // 2.父进程控制子进程
    ControlChannel(channels, 5);

    // 3.关闭管道和子进程
    CleanChannel(channels);

    return 0;
}